/*
 * file_manage.c
 *
 *  Created on: 06/10/2013
 *      Author: AXL
 */

#include "file_manage.h"

/**
 * Indica si el archivo en la ruta `filename` existe.
 * RETURN 	0	el arhivo no existe.
 */
int fileExists( const char* filename )
{
	struct stat buffer;
	return (stat( filename, &buffer ) == 0);
}

/**
 * Crea una copia exacta de un archivo `source` en `copy`.
 * Si el archivo en `copy` no existe, lo crea.
 * RETURN	ERROR	no se realizo la copia correctamente.
 * 				0	copia realizada con exito.
 */
int fileCopy( const char* copy, const char* source )
{
	int status, res;
	FILE *s, *c;
	char* data;

	s = fopen( source, "r" );
	if ( !s )
		return ERROR;
	c = fopen( copy, "w" );
	if ( !c )
	{
		fclose( s );
		return ERROR;
	}

	while ( !feof( s ) )
	{
		data = getLine( s, &res );
		if ( !data )
			return ERROR;
		if ( res != -1 )
			fprintf( c, "%s", data );
		free( data );

		if ( !feof( s ) ) /* evitamos agregar un '\n' de mas al final */
			fprintf( c, "%c", '\n' );
	}

	status = ( ferror( c ) || ferror( s ));

	fclose( c );
	fclose( s );

	if ( status )
		return ERROR;
	return 0;
}

/**
 * Crea un archivo en la ruta `path`.
 * RETURN	ERROR	no se pudo crear el archivo.
 */
int createFile( const char* path )
{
	FILE* file = fopen( path, "w" );

	if ( !file )
		return ERROR;

	fclose( file );

	return 0;
}

/**
 * Lee una linea de un archivo `file`.
 * PRE: 	`file` es un puntero a archivo correctamente abierto.
 * POST:	El usuario se debe encargar de liberar la memoria alocada devuelta.
 * 			`status` en -1 significa que no leyo nada y devuelve string vacia.
 * RETURN 	char*	puntero a la cadena leida.
 * 			NULL	no se pudo leer la linea.
 */
char* getLine( FILE* file, int* status )
{
	char *buffer, *temp_buffer;
	int index = 0;
	int read_len = 0;
	size_t size = sizeof(char) * BUF_LEN;

	buffer = malloc( size + sizeof(char) ); /* +1 char para '\0' */
	if ( !buffer )
		return NULL;

	buffer[0] = '\0';
	*status = 0;

	while ( fscanf( file, "%"STR(BUF_LEN)"[^\n]%n", buffer + index, &read_len )
			!= EOF )
	{
		if ( read_len < BUF_LEN ) /* se alcanzo un \n */
			break;
		index += read_len;

		temp_buffer = realloc( buffer, stringSize( buffer ) + size );

		if ( !temp_buffer )
		{
			free( buffer );
			return NULL;
		}
		buffer = temp_buffer;
	}
	if ( feof( file ) && strlen( buffer ) == 0 )
		*status = -1; /* no se leyo nada */

	fscanf( file, "%*c" ); /* saltear \n */

	return buffer;
}

/**
 * Posiciona el puntero de lectura del archivo en la siguiente linea no vacia.
 * PRE:		`file` es un puntero a archivo correctamente abierto.
 * RETURN		0	ejecucion correcta.
 * 			ERROR	ocurrio algun error.
 */
int skipLine( FILE* file )
{
	fscanf( file, "%*[^\n\r]%*[\n\r]" );

	if ( ferror( file ) )
		return ERROR;

	return 0;
}

/**
 * Lee una 'string' de `file` y valida que sea una fecha (formato ISO 8601).
 * PRE:		`file` apunta a un archivo correctamente abierto.
 * POST:	De resultar en una lectura correcta, el usuario debe liberar la
 * 			memoria de a cadena devuelta.
 * RETURN	NULL 	el valor leido en `file` no es una fecha valida o hubo
 * 					un error.
 * 			char*	puntero a una cadena con la fecha.
 */
char* readDate( FILE* file )
{
	char* date = malloc( sizeof(char) * (LEN_DATE + 1) ); /* +1 para '\0' */

	if ( !date )
		return NULL;

	if ( fscanf( file, "%"STR(LEN_DATE)"c", date ) == EOF
			|| strlen( date ) != LEN_DATE || !isDateTimeString( date ) )
	{
		free( date );
		return NULL;
	}

	return date;
}

/**
 * Vacia y destruye un V_Array cargado con fileToArray().
 * PRE:		el array contiene punteros a string.
 * POST:	array destruido y memoria de strings liberada.
 */
void clearFileArray( V_Array* array )
{
	int i, len;
	line_t line;

	len = VA_count( *array );

	for ( i = 0; i < len; i++ )
	{
		VA_get_i( *array, i, &line );
		free( line.text );
	}
	VA_destroy( array );
}

/**
 * Guarda un archivo de texto en un V_Array `array` en forma de `line_t` con el
 * contenido de cada una de sus lineas.
 * PRE:		array creado para contener `line_t`, ruta es la ruta a un archivo.
 * POST:	cada posicion del array corresponde a una linea del archivo.
 * 			Si hubo algun error el array queda vacio.
 * RETURN 	ERROR 	no se pudo completar la escritura.
 */
int fileToArray( const char* filename, V_Array* array )
{
	int status, ret_code = 0;
	line_t line;
	FILE* file = fopen( filename, "r" );

	if ( !file )
		return ERROR;

	while ( !feof( file ) )
	{
		line.text = getLine( file, &status );
		if ( status == -1 )
		{
			free( line.text );
			break;
		}
		if ( !line.text || VA_add( array, &line ) == -1 )
		{
			clearFileArray( array );
			ret_code = ERROR;
			break;
		}
	}
	fclose( file );

	return ret_code;
}

/**
 * Libera la memoria de todos los elementos de `list` y la vacia.
 * PRE:		`list` fue creada para contener line_t.
 * POST:	`list` vacia y memoria liberada.
 */
void fileListDestroy( TListaSimple* list )
{
	line_t line;

	if ( L_Vacia( *list ) )
		return;

	L_Mover_Cte( list, L_Primero );
	do
	{
		L_Elem_Cte( *list, &line );
		free( line.text );
	}
	while ( L_Mover_Cte( list, L_Siguiente ) == TRUE );

	L_Vaciar( list );
}

/**
 * Guarda el archivo en `path` linea por linea, gurdandolas en `list` (los '\n'
 * NO SE GURDAN) en forma de `line_t`.
 * PRE:		`list` fue creada para contener `line_t` y `path` ruta a un archivo
 * POST:	La memoria de las lineas leidas debe ser liberada por el usuario
 * 			(ver fileListDestroy(...)).
 * RETURN	ERROR	fallo la carga de datos y la lista queda vacia.
 */
int fileAsList( TListaSimple* list, const char* path )
{
	line_t data;
	FILE* file = fopen( path, "r" );
	int status, counter = 1;

	if ( !file )
		return ERROR;

	while ( !feof( file ) )
	{
		data.index = counter;
		data.text = getLine( file, &status );
		if ( !data.text )
			return ERROR;
		if ( status == -1 )
			break;

		if ( L_Insertar_Cte( list, L_Siguiente, &data ) == FALSE )
		{
			fileListDestroy( list );
			return ERROR;
		}
		counter++;
	}
	fclose( file );
	return 0;
}

/**
 * Mueve el contenido `line_t` desde `source` a `dest` hasta (sin incluir) `end`
 * PRE:		`dest` creada y soure contiene `line_t`.
 * POST:	`source` apunta al nodo con `line_t` de index `end`.
 * RETURN	ERROR	`source` no llegaba a la linea `end`
 */
int moveData( TCola* dest, TListaSimple* source, int end )
{
	line_t line;

	L_Elem_Cte( *source, &line );
	while ( line.index < end )
	{
		C_Agregar( dest, &line.text );
		if ( L_Mover_Cte( source, L_Siguiente ) == FALSE )
			return ERROR;
		L_Elem_Cte( *source, &line );
	}
	return 0;
}

/**
 * Carga las `line_t` de `src` en `dest` salteando las que tienen un `index`
 * igual al de algun elemeto de `matches`.
 * PRE:		`matches` y `src` tienen los corrientes posicionados desde donde
 * 			se quiere comenzar el intrcambio.
 * RETURN	ERROR	no habia memoria suficiente.
 */
int insertInto( TCola* dest, TListaSimple* matches, TListaSimple* src, int end )
{
	line_t line;

	L_Elem_Cte( *src, &line );
	if ( line.index == end )
	{
		if ( L_Vacia( *matches ) == FALSE
				&& L_Mover_Cte( matches, L_Siguiente ) == TRUE ) /* siguiente coincidencia */
		{
			L_Elem_Cte( *matches, &end );
		}
	}
	else if ( C_Agregar( dest, &line ) == FALSE ) /* agregar linea */
		return ERROR;

	if ( L_Mover_Cte( src, L_Siguiente ) == TRUE )
		return insertInto( dest, matches, src, end );

	return 0;
}

/**
 * Guarda en `matches` las lineas iguales entre `a` y `b` en las listas.
 * PRE:		`match_a/b` creada para almacenar `int`.
 * 			`a` y `b` listas con `line_t`.
 * 			La comapracion se hace desde las lineas donde esta el corriente en
 * 			cada lista.
 * RETURN	ERROR	no se obtuvieron las diferencias y `match_a/b` queda vacia.
 */
int getMatches( TListaSimple* match_a, TListaSimple* match_b, TListaSimple* a,
		TListaSimple* b )
{
	line_t data_a, data_b;

	if ( L_Vacia( *a ) == TRUE || L_Vacia( *b ) == TRUE )
		return 0;

	L_Elem_Cte( *a, &data_a );
	L_Elem_Cte( *b, &data_b );

	if ( strcmp( data_a.text, data_b.text ) != 0 )
	{
		if ( L_Mover_Cte( b, L_Siguiente ) == FALSE )
		{
			if ( L_Mover_Cte( a, L_Siguiente ) == TRUE )
				L_Mover_Cte( b, L_Primero );
			else
				return 0;
		}
		return getMatches( match_a, match_b, a, b );
	}
	else
	{
		if ( L_Insertar_Cte( match_a, L_Siguiente, &data_a.index ) == FALSE
				|| L_Insertar_Cte( match_b, L_Siguiente, &data_b.index )
						== FALSE )
		{
			L_Vaciar( match_a );
			L_Vaciar( match_b );
			return ERROR;
		}
		if ( L_Mover_Cte( a, L_Siguiente ) == TRUE )
			return getMatches( match_a, match_b, a, b );
	}
	return 0;
}
